# -*- coding: UTF-8 -*-

import datetime
import time
import os
from common import printLog,TradeSide,getMinOrderQty,simpleTWAP
from DMAClass import PriceType,DMAOrder,OrderBook
from config import DMA_DIR
from strategySignal import ALL_ACCOUNT_MOVE_POSITIONS

'''
OneQuant DEMO:DMA母单导入示例

demo场景:完成对一篮子股票买卖目标数量的执行

重要:在测试环境使用时,请修改 config.py及strategySignal.py文件中相应的 DMA_DIR 目录及账号信息。
     相关使用规范请查阅【DMA使用手册】

本示例完成
    1:对策略文件strategySignal输出的调仓信息，执行类似TWAP的拆单写入DMA报单文件
    2:构造一个订单薄实例，其维护订单的状态，比如 轮询读取订单回报，计算订单的已成交数量等等
    3:当完成目标数量时，停止下单
    4:本示例为简单展示如何使用DMA,部分逻辑尚需完善,不建议直接使用于生产环境
    5:用户在写入DMA导入文件时，请确保传入的本地订单号custBatchNo 为32位无符号整型(uint32)且为当天唯一

'''

def order_dma(fd,dmaOrder:DMAOrder):

    dma_order_str=dmaOrder.toCSVFormat()
    fd.write(dma_order_str+"\n")   # 写入DMA文件 
    printLog("写入报单文件:[%-5s]:股票:%s,价格:%s,数量:%d,上浮ticks:%d" % ( TradeSide(dmaOrder.tradeSide).name,dmaOrder.symbol,PriceType(dmaOrder.priceType).name,dmaOrder.targetQty,dmaOrder.ticks),flush = True)

def cancel_dma(fd,order:DMAOrder):

    updTime=datetime.datetime.now().strftime("%Y-%m-%d %H%M%S.%f")[11:-3]   # hhmmss.sss
    cancel_dma_str="%s,%s" % (updTime,order.custBatchNo)
    fd.write(cancel_dma_str+"\n")      # 写入撤单文件
    printLog('写入撤单文件:' + cancel_dma_str,flush = True)

def init_order_cancel_files(order_file,cancel_file):
    if not os.path.exists(order_file):
        with open(order_file,'w') as f:
            f.write("updTime,custBatchNo,acctType,acct,symbol,tradeSide,targetQty,algoId,priceType,ticks,highLimitPrice,lowLimitPrice" + "\n")
        printLog('创建DMA下单文件:' + order_file)
    if not os.path.exists(cancel_file):
        with open(cancel_file,'w') as f:
            f.write("updTime,custBatchNo" + "\n")
        printLog('创建DMA撤单文件:' + cancel_file)


if __name__ == '__main__':
    
    order_params="""
    custBatchNo=1
    acct=S00000099
    acctType=S0
    algoId=DMA
    symbol=000000.SH
    priceType=P8
    targetQty=100
    tradeSide=1
    ticks=0
    lowLimitPrice=0
    highLimitPrice=0
    """
    
    order_params = {kv.split("=")[0]:kv.split("=")[1] for kv in order_params.replace(" ","").split()}

    # 初始化下单文件及撤单文件,订单簿orderbook,启动orderbook
    date= datetime.datetime.now().strftime("%Y%m%d")
    order_file =os.path.join(DMA_DIR,    'algoOrder_' + date + '.csv')
    cancel_file=os.path.join(DMA_DIR,'algoOrderStop_' + date + '.csv')

    init_order_cancel_files(order_file,cancel_file)
    
    orderbook=OrderBook(DMA_DIR,date)
    orderbook.init_history()
    orderbook.run()             # 后台运行 读取各个回写文件

    try:
        with open(order_file,'a',buffering=1) as order_f,open(cancel_file,'a',buffering=1) as cancel_f:         # 注意只能追加模式
            
            positionMoves4DMA={}  
            for (acctType,acct),positionMoves in ALL_ACCOUNT_MOVE_POSITIONS.items():
                print((acctType,acct),positionMoves)
                for pm in positionMoves:
                    symbol,tradeSide,targetVol = pm
                    move= {"acct":acct,"acctType":acctType,"symbol":symbol,"targetVol":targetVol,"tradeSide":tradeSide}
                    key= (acct,acctType,symbol,tradeSide,targetVol)
                    positionMoves4DMA[key]=move

            unfinished_stocks= positionMoves4DMA.copy()

            executeMins=5                   # 每个票执行时长
            sliceDuratonMins=0.5            # 拆单切片时长
            
            start=time.time()

            updTime=datetime.datetime.now().strftime("%Y-%m-%d %H%M%S.%f")[11:-3]

            while len(unfinished_stocks)>0:  # and time.time()-start<(executeMins+2)*60+1: # and updTime<='150000':
                
                if updTime>'113000' and updTime<'130000':
                    print("%s:午间休市..." % updTime)
                    time.sleep(sliceDuratonMins*60)
                    updTime=datetime.datetime.now().strftime("%Y-%m-%d %H%M%S.%f")[11:-3]
                    continue
                unfinished_stocks_copy=unfinished_stocks.copy()
                for pm in unfinished_stocks_copy:
                    (acct,acctType,stock,tradeSide,totalTargetQty ) = pm
                    printLog(str(pm))
                    lotsize= getMinOrderQty(stock)
                    twap_qty = simpleTWAP(stock,totalTargetQty,executeMins,sliceDuratonMins) 
                    updTime=datetime.datetime.now().strftime("%Y-%m-%d %H%M%S.%f")[11:-3]
                    custBatchNo=   int(float(updTime)*pow(10,3)) + int(stock[0:6])

                    order_params['acct'] = acct
                    order_params['acctType'] = acctType
                    order_params['symbol'] = stock
                    order_params['custBatchNo'] = custBatchNo
                    order_params['tradeSide'] = tradeSide.value
                    order_params['updTime']   = updTime
                    # 默认价格策略1: 买:买1价  或 卖: 卖1价
                    order_params['priceType'] = PriceType.BID_1.value  if TradeSide.isBuy(tradeSide) else PriceType.ASK_1.value
                    order_params['ticks']=0  if TradeSide.isBuy(tradeSide) else 0                          

                    effetiveQty,filledQty,pendingQty,canceledQty=orderbook.getQtySummary(stock,tradeSide,order_params['acct'])     # 计算有效委托数量(pending数量+已完成量)、已完成数量、当前pending数量

                    # 下单数量
                    qty= max(min(twap_qty,totalTargetQty-filledQty-pendingQty),0)
                    order_params['targetQty']= qty

                    progress = filledQty*100.0/totalTargetQty
                    effectiveProgress = (effetiveQty-canceledQty)*1.0/totalTargetQty
                    filledRate = 0
                    cancelRate = 0
                    if effetiveQty>0:
                        filledRate = filledQty*1.0/effetiveQty
                        cancelRate = canceledQty*1.0/effetiveQty

                    printLog('%s->股票:%s,%-4s,累计委托:%d,累计成交:%d,挂单数量:%d,完成度:%.2f%%,成交率:%.3f,撤单率:%.3f' % (acct,stock,tradeSide.name,effetiveQty,filledQty,pendingQty,progress,filledRate,cancelRate),flush = True)
                    
                    if filledQty>=totalTargetQty:
                        if pm in unfinished_stocks:
                            unfinished_stocks.pop(pm)
                        continue
                    # 价格策略2：成交率过低时 采用主动价格:对手一价进行主动买入
                    if filledRate<0.5:                        
                        order_params['priceType'] = PriceType.ASK_1.value if TradeSide.isBuy(tradeSide) else PriceType.BID_1.value
                        order_params['ticks']=0
                    #价格策略3：撤单率过高时 采用主动价格:对手一价 浮动 1 tick
                    if cancelRate>0.3:                        
                        order_params['priceType'] = PriceType.ASK_1.value if TradeSide.isBuy(tradeSide) else PriceType.BID_1.value
                        order_params['ticks']=1 if TradeSide.isBuy(tradeSide) else -1

                    # 撤单控制
                    if qty<lotsize or (effectiveProgress>0.5 and filledRate<0.5):            # 已委托数量超过一半且成交率过低时,撤销最不利价格的一单
                        unfinished_orders = orderbook.getUnfinishOrders(stock,tradeSide,order_params['acct'])
                        if len(unfinished_orders)>0:
                            unfinished_orders_sorted= sorted(unfinished_orders,key=lambda order:order.orderPrice if order.tradeSide== TradeSide.BUY.value else order.orderPrice*-1)
                            for order in unfinished_orders_sorted[0:1]:
                                cancel_dma(cancel_f,order)

                            if effectiveProgress>0.8 and len(unfinished_orders_sorted)>1:   # 委托进度逼近总目标量时,有更多未撤订单时,进行全部撤单
                                for order in unfinished_orders_sorted[1:]:
                                    cancel_dma(cancel_f,order)
                    # 下单
                    if (qty>=lotsize and TradeSide.isBuy(tradeSide)) or (qty>=0 and TradeSide.isSell(tradeSide)) :
                        dma_order = DMAOrder()
                        dma_order.parseDMAOrder(order_params)
                        order_dma(order_f,dma_order)

                time.sleep(sliceDuratonMins*60)

    except Exception as e:
        printLog(str(e))
        raise(e)
    
    finally:
        orderbook.stop()
